
package com.dotools.utils;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.net.Uri;
import android.util.TypedValue;
import android.view.View;

import com.dotools.Native;
import com.dotools.base.CommonConstants;
import com.dotools.compat.MissedAPI;
import com.dotools.debug.LOG;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class BitmapUtils {
    public static class Size {
        public int width;
        public int height;

        public Size(int w, int h) {
            this.width = w;
            this.height = h;
        }
    }

    private static final boolean LOGV = CommonConstants.IS_DEBUG;
    
    public static Options option565 = new Options();
     static {
        option565.inPreferredConfig = Bitmap.Config.RGB_565;
    }

    // -------------------------------------------------------------------------

    public static Bitmap createSnapshot(View v, Float scale) {
        Bitmap result = null;
        if (v.getWidth() > 0 && v.getHeight() > 0) {
            try {
                final int bmpWidth = (int) (v.getWidth() * scale);
                final int bmpHeight = (int) (v.getHeight() * scale);

                LOG.logI("w=" + bmpWidth + ", h=" + bmpHeight + ", s=" + scale);

                result = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);

                Canvas bmpCanvas = new Canvas(result);
                bmpCanvas.translate(-v.getScrollX(), -v.getScrollY());
                bmpCanvas.scale(scale, scale);

                v.cancelLongPress();
                v.requestFocus();
                v.draw(bmpCanvas);
            } catch (OutOfMemoryError e) {
                LOG.logE("OOM~~", e);
            }
        }

        return result;
    }

    // -------------------------------------------------------------------------

    /**
     * do not preserve ratio zoomed image is exactly w * h pixels
     */
    public static final int ZOOM_FLAGS_NORMAL = 0;

    /**
     * zoom the image into largest with ratio, fit into w/h, no crop zoomed image would be smaller than w * h pixels,
     * one of the edge will exactly as w or h
     */
    public static final int ZOOM_FLAGS_FIT = 1;

    /**
     * zoom the image to width to 'w', keep ratio, height might be cropped to fit target ratio zoomed image width = w,
     * height will be determined by source ratio
     */
    public static final int ZOOM_FLAGS_FILL_WIDTH = 2;

    /**
     * zoom the image to height to 'h', keep ratio, width might be cropped to fit target ratio zoomed image height = h,
     * width will be determined by source ratio
     */
    public static final int ZOOM_FLAGS_FILL_HEIGHT = 3;

    /**
     * zoom the image into largest with ratio, fit into w/h, one of the width/height might be cropped to keep the other
     * side largest zoomed image will be as large as possible to ensure (width == w || height == h), the other side is
     * determined by ratio
     */
    public static final int ZOOM_FLAGS_FILL = 4;

    static boolean isZero(float z) {
        return z > -0.0001 && z < 0.0001;
    }

    private static volatile Matrix sScaleMatrix;

    public static Bitmap zoomBitmap(Bitmap bitmap, boolean recycle, float ratio) {
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();
        return zoomBitmap(bitmap, recycle, (int) (w * ratio), (int) (h * ratio), ZOOM_FLAGS_NORMAL);
    }

    public static Bitmap zoomBitmap(Bitmap bitmap, boolean recycle, int w, int h) {
        return zoomBitmap(bitmap, recycle, w, h, ZOOM_FLAGS_NORMAL);
    }

    public static Bitmap zoomBitmap(Bitmap bitmap, boolean recycle, int w, int h, int flags) {
        if(bitmap == null || bitmap.isRecycled()) {
            return null;
        }
        final int width = bitmap.getWidth();
        final int height = bitmap.getHeight();

        if (w == width && h == height) {
            LOG.logI("Bitmap: " + bitmap + " is already required size: w=" + w + ", h=" + h);
            return bitmap;
        }

        final float ratioSrc = ((float) width) / height;
        final float ratioDst = ((float) w) / h;

        final int origFlags = flags;

        if (isZero(ratioSrc - ratioDst)) {
            flags = ZOOM_FLAGS_NORMAL;
        }

        if (ZOOM_FLAGS_FILL == flags) {
            if (ratioSrc > ratioDst) {
                flags = ZOOM_FLAGS_FILL_HEIGHT;
            } else {
                flags = ZOOM_FLAGS_FILL_WIDTH;
            }
        }

        float targetWidth = w;
        float targetHeight = h;

        switch (flags) {
            case ZOOM_FLAGS_FIT: {
                if (ratioSrc > ratioDst) {
                    // keep w
                    targetWidth = w;
                    targetHeight = w / ratioSrc;
                } else {
                    // keep h
                    targetWidth = h * ratioSrc;
                    targetHeight = h;
                }
                break;
            }
            case ZOOM_FLAGS_FILL_WIDTH: {
                // keep w
                targetWidth = w;
                targetHeight = w / ratioSrc;
                break;
            }
            case ZOOM_FLAGS_FILL_HEIGHT: {
                // keep h
                targetWidth = h * ratioSrc;
                targetHeight = h;
                break;
            }
            case ZOOM_FLAGS_FILL: {
                throw new RuntimeException("fill both should be resolved to fill width / height");
            }
            case ZOOM_FLAGS_NORMAL:
            default: {
                targetWidth = w;
                targetHeight = h;
                break;
            }
        }

        float scaleWidht = ((float) targetWidth / width);
        float scaleHeight = ((float) targetHeight / height);

        if (LOGV) {
            LOG.logI("of=" + origFlags + "\tf=" + flags + ",\tw=" + w + ",\th=" + h + ",\tbw=" + width + ",\tbh="
                    + height + ",\ttw=" + targetWidth + ",\tth=" + targetHeight + ",\tsw=" + scaleWidht + ",\tsh="
                    + scaleHeight + ",\tkr=" + isZero(scaleHeight / scaleWidht - 1));
        }

        Matrix m = null;
        synchronized (BitmapUtils.class) {
            // small pool of just 1 matrix
            m = sScaleMatrix;
            sScaleMatrix = null;
        }

        if (m == null) {
            m = new Matrix();
        }

        m.setScale(scaleWidht, scaleHeight);

        Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, true);

        synchronized (BitmapUtils.class) {
            // do we need to check for null? why not just assign everytime?
            if (sScaleMatrix == null) {
                sScaleMatrix = m;
            }
        }

        if (recycle && bitmap.isMutable()) {
            bitmap.recycle();
        }

        if (LOGV) {
            LOG.logI("zoom: inWidth=" + width + ", inHeight=" + height + ", outWidth=" + result.getWidth()
                    + ", outHeight=" + result.getHeight());
        }

        return result;
    }

    // -------------------------------------------------------------------------

    public static Bitmap drawableToBitmap(Drawable drawable) {
    	if(drawable == null) {
            return null;
        }
        return drawableToBitmap(drawable, drawable.getIntrinsicWidth(),
                drawable.getIntrinsicHeight());
    }

    public static Bitmap drawableToBitmap(Drawable drawable, int width, int height) {
        if(drawable == null) {
            return null;
        }
        try {
            Bitmap bitmap = Bitmap.createBitmap(width, height,
                    drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
                            : Bitmap.Config.RGB_565);

            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, width, height);
            drawable.draw(canvas);

            return bitmap;
        } catch (Exception e) {
            return ((BitmapDrawable)drawable).getBitmap();
        }
    }
    public static Bitmap drawableToBitmapConfig565(Drawable drawable, int width, int height) {
        if(drawable == null) {
            return null;
        }
        try {
            Bitmap bitmap = Bitmap.createBitmap(width, height,Bitmap.Config.RGB_565);
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, width, height);
            drawable.draw(canvas);

            return bitmap;
        } catch (Exception e) {
            return ((BitmapDrawable)drawable).getBitmap();
        }
    }

    // -------------------------------------------------------------------------

    public static void gingerBlur(Bitmap bmpToBlur, int radius) {
        if (bmpToBlur == null || bmpToBlur.isRecycled()) {
            throw new RuntimeException("Bad bitmap: " + bmpToBlur + " is recycled.");
        }

        if (!bmpToBlur.isMutable()) {
            throw new RuntimeException("Bad bitmap: " + bmpToBlur + " is not mutable.");
        }

        Native.gingerBlur(bmpToBlur, radius);
    }

    public static void simpleBlur(Bitmap bmpToBlur, int radius) {
        if (bmpToBlur == null || bmpToBlur.isRecycled()) {
            throw new RuntimeException("Bad bitmap: " + bmpToBlur + " is recycled.");
        }

        if (!bmpToBlur.isMutable()) {
            throw new RuntimeException("Bad bitmap: " + bmpToBlur + " is not mutable.");
        }

        if (bmpToBlur.getConfig() != Bitmap.Config.ARGB_8888) {
            throw new RuntimeException("Bad bitmap: " + bmpToBlur + " : only ARGB_8888 is supported");
        }

        Native.simpleBlur(bmpToBlur, radius);
    }

    public static void stackBlur(Bitmap bmpToBlur, int radius) {
        if (bmpToBlur == null || bmpToBlur.isRecycled()) {
            throw new RuntimeException("Bad bitmap: " + bmpToBlur + " is recycled.");
        }

        if (!bmpToBlur.isMutable()) {
            throw new RuntimeException("Bad bitmap: " + bmpToBlur + " is not mutable.");
        }

        if (bmpToBlur.getConfig() != Bitmap.Config.ARGB_8888) {
            throw new RuntimeException("Bad bitmap: " + bmpToBlur + " : only ARGB_8888 is supported");
        }

        Native.stackBlur(bmpToBlur, radius);
    }

    // should removed from public: too too slow, memory requirement too large
    public static void javaBlur(Bitmap origBitmap, int radius) {
        if (radius < 1 || origBitmap == null || origBitmap.getWidth() <= 0 || origBitmap.getHeight() <= 0) {
            return;
        }

        Bitmap outBitmap = origBitmap;
        int w = outBitmap.getWidth();
        int h = outBitmap.getHeight();

        int[] pix = new int[w * h];
        outBitmap.getPixels(pix, 0, w, 0, 0, w, h);

        int wm = w - 1;
        int hm = h - 1;
        int wh = w * h;
        int div = radius + radius + 1;

        int r[] = new int[wh];
        int g[] = new int[wh];
        int b[] = new int[wh];
        int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
        int vmin[] = new int[Math.max(w, h)];

        int divsum = (div + 1) >> 1;
        divsum *= divsum;
        int dv[] = new int[256 * divsum];
        for (i = 0; i < 256 * divsum; i++) {
            dv[i] = (i / divsum);
        }

        yw = yi = 0;

        int[][] stack = new int[div][3];
        int stackpointer;
        int stackstart;
        int[] sir;
        int rbs;
        int r1 = radius + 1;
        int routsum, goutsum, boutsum;
        int rinsum, ginsum, binsum;

        for (y = 0; y < h; y++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            for (i = -radius; i <= radius; i++) {
                p = pix[yi + Math.min(wm, Math.max(i, 0))];
                sir = stack[i + radius];
                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);
                rbs = r1 - Math.abs(i);
                rsum += sir[0] * rbs;
                gsum += sir[1] * rbs;
                bsum += sir[2] * rbs;
                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }
            }
            stackpointer = radius;

            for (x = 0; x < w; x++) {

                r[yi] = dv[rsum];
                g[yi] = dv[gsum];
                b[yi] = dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (y == 0) {
                    vmin[x] = Math.min(x + radius + 1, wm);
                }
                p = pix[yw + vmin[x]];

                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[(stackpointer) % div];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi++;
            }
            yw += w;
        }
        for (x = 0; x < w; x++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            yp = -radius * w;
            for (i = -radius; i <= radius; i++) {
                yi = Math.max(0, yp) + x;

                sir = stack[i + radius];

                sir[0] = r[yi];
                sir[1] = g[yi];
                sir[2] = b[yi];

                rbs = r1 - Math.abs(i);

                rsum += r[yi] * rbs;
                gsum += g[yi] * rbs;
                bsum += b[yi] * rbs;

                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }

                if (i < hm) {
                    yp += w;
                }
            }
            yi = x;
            stackpointer = radius;
            for (y = 0; y < h; y++) {
                // Preserve alpha channel: ( 0xff000000 & pix[yi] )
                pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (x == 0) {
                    vmin[y] = Math.min(y + r1, hm) * w;
                }
                p = x + vmin[y];

                sir[0] = r[p];
                sir[1] = g[p];
                sir[2] = b[p];

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[stackpointer];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi += w;
            }
        }

        outBitmap.setPixels(pix, 0, w, 0, 0, w, h);
    }

    // -------------------------------------------------------------------------

    public static Bitmap createRoundCornerBitmap(Bitmap bitmap, float roundPx) {

        if(bitmap == null || bitmap.isRecycled()) {
            return null;
        }
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(output);

        final int color = 0xff424242;
        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        final RectF rectF = new RectF(rect);

        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);

        return output;
    }

    // -------------------------------------------------------------------------

    // 获得带倒影的图片
    public static Bitmap createReflectionImage(Bitmap bmpSrc) {
        return createReflectionImage(bmpSrc, 0.4f);
    }

    public static Bitmap createReflectionImage(Bitmap bmpSrc, float reflectionRatio) {
        return createReflectionImage(bmpSrc, reflectionRatio, 4);
    }

    public static Bitmap createReflectionImage(Bitmap bmpSrc, float reflectionRatio, int reflectionGap) {
        if (reflectionRatio <= 0 || reflectionRatio > 1.0f) {
            return null;
        }

        if(bmpSrc == null || bmpSrc.isRecycled()) {
            return null;
        }
        final int width = bmpSrc.getWidth();
        final int height = bmpSrc.getHeight();
        final int reflectHeight = (int) (height * reflectionRatio);

        Matrix matrix = new Matrix();
        matrix.preScale(1, -1);

        Bitmap reflectionImage = Bitmap.createBitmap(bmpSrc, 0, height - reflectHeight, width, reflectHeight, matrix,
                false);

        Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + reflectHeight), Config.ARGB_8888);

        // draw original
        Canvas canvas = new Canvas(bitmapWithReflection);
        canvas.drawBitmap(bmpSrc, 0, 0, null);

        Paint deafalutPaint = new Paint();
        canvas.drawRect(0, height, width, height + reflectionGap, deafalutPaint);

        canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null);

        Paint paint = new Paint();
        LinearGradient shader = new LinearGradient(0, bmpSrc.getHeight(), 0, bitmapWithReflection.getHeight()
                + reflectionGap, 0x70ffffff, 0x00ffffff, TileMode.CLAMP);
        paint.setShader(shader);
        // Set the Transfer mode to be porter duff and destination in
        paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
        // Draw a rectangle using the paint with our linear gradient
        canvas.drawRect(0, height, width, bitmapWithReflection.getHeight() + reflectionGap, paint);

        if (LOGV) {
            LOG.logI("reflection: inWidth=" + width + ", inHeight=" + height + ", outWidth="
                    + bitmapWithReflection.getWidth() + ", outHeight=" + bitmapWithReflection.getHeight());
        }

        return bitmapWithReflection;
    }

    // -------------------------------------------------------------------------

    public static Bitmap rotateBitmap(Bitmap source, int rotation, boolean recycle) {

        if(source == null || source.isRecycled()) {
            return null;
        }
        if (rotation == 0)
            return source;

        int w = source.getWidth();
        int h = source.getHeight();

        Matrix m = new Matrix();
        m.postRotate(rotation);

        Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, w, h, m, true);

        if (recycle)
            source.recycle();

        return bitmap;
    }

    // -------------------------------------------------------------------------

    private static final int DEFAULT_QUALITY = 90;

    public static byte[] compressToBytes(Bitmap bitmap, CompressFormat format) {
        return compressToBytes(bitmap, format, DEFAULT_QUALITY);
    }

    public static byte[] compressToBytes(Bitmap bitmap, CompressFormat format, int quality) {
        if (bitmap == null || bitmap.isRecycled()) {
            LOG.logE("bad bitmap: " + bitmap, new Exception());
            return null;
        }

        ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
        bitmap.compress(format, quality, baos);
        return baos.toByteArray();
    }

    public static void compressToFile(Bitmap bitmap, File file, CompressFormat format) {
        compressToFile(bitmap, file, format, DEFAULT_QUALITY);
    }

    public static void compressToFile(Bitmap bitmap, File file, CompressFormat format, int quality) {
        if (bitmap == null || bitmap.isRecycled()) {
            LOG.logE("bad bitmap: " + bitmap, new Exception());
            return;
        }

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            bitmap.compress(format, quality, fos);
        } catch (Exception e) {
            LOG.logI("could not write file", e);
        } finally {
            Utilities.silentlyClose(fos);
        }
    }

    public static void compressToStream(Bitmap bitmap, OutputStream os, CompressFormat format) {
        compressToStream(bitmap, os, format, DEFAULT_QUALITY);
    }

    public static void compressToStream(Bitmap bitmap, OutputStream os, CompressFormat format, int quality) {
        if (bitmap == null || bitmap.isRecycled()) {
            LOG.logE("bad bitmap: " + bitmap, new Exception());
            return;
        }

        try {
            bitmap.compress(format, quality, os);
        } catch (Exception e) {
            LOG.logI("could not write to stream", e);
        } finally {
        }
    }

    // -------------------------------------------------------------------------
    public static Bitmap cutBound(Bitmap bitmap,int px, Bitmap.Config cfg) {

        if(bitmap == null || bitmap.isRecycled()) {
            return null;
        }
        int sourceWidth = bitmap.getWidth();
        int sourceHeight = bitmap.getHeight();
        
        final Bitmap bitmap1 = Bitmap.createBitmap(sourceWidth - px*2, sourceHeight - px*2, cfg);
        final Canvas canvas = new Canvas(bitmap1);
        Paint p = new Paint();
        p.setAntiAlias(true);
        canvas.drawBitmap(bitmap, new Rect(px, px, sourceWidth - px, sourceHeight - px), new Rect(0, 0,sourceWidth - px * 2, sourceHeight - px * 2), p);
        return bitmap1;
    }
    /**
     * 放大icon，扩充的区域用透明来填充
     */
    public static Bitmap scaleBitmapTransparent(Bitmap icon, int w, int h, boolean recycle) {

        if(icon == null || icon.isRecycled()) {
            return null;
        }
        if (w <= 0 || h <= 0) {
            return icon;
        }

        int sourceWidth = icon.getWidth();
        int sourceHeight = icon.getHeight();

        if (sourceWidth >= w || sourceHeight >= h) {
            return scaleBitmap(icon, w, h, recycle);
        }

        final Bitmap bitmap = Bitmap.createBitmap(w, h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas();
        canvas.setBitmap(bitmap);

        float sx = w/(float)sourceWidth;
        float sy = h/(float)sourceHeight;

        if (sx > 1.0 || sy > 1.0) {
            final int left = (w - sourceWidth) / 2;
            final int top = (h - sourceHeight) / 2;
            canvas.drawBitmap(icon, left, top, null);
        } else {
            Matrix matrix = new Matrix();
            matrix.setScale(sx, sy);
            canvas.drawBitmap(icon, matrix, null);
        }
        MissedAPI.Canvas_setBitmap(canvas, null);
        if (recycle) {
            icon.recycle();
        }

        return bitmap;
    }

    /**
     * @param bitmap
     * @param w
     * @param h
     * @param recycle 当确定bitmap可以被recycle时，才设为true
     * @return
     */
    public static Bitmap scaleBitmap(Bitmap bitmap, int w, int h, boolean recycle) {
        return scaleBitmap(bitmap, w, h, Bitmap.Config.ARGB_8888, recycle);
    }

    public static Bitmap scaleBitmap(Bitmap bitmap, int w, int h, Config config, boolean recyle) {

        if(bitmap == null || bitmap.isRecycled()) {
            return null;
        }
        Bitmap newBitmap = Bitmap.createBitmap(w, h, config);
        if (newBitmap == null) {
            return null;
        }
        newBitmap.setDensity(bitmap.getDensity());
        Canvas canvas = new Canvas(newBitmap);
        canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
                | Paint.FILTER_BITMAP_FLAG));
        canvas.drawBitmap(bitmap, new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()),
                new Rect(0, 0, w, h), null);
        if(recyle) {
            bitmap.recycle();
        }
        MissedAPI.Canvas_setBitmap(canvas, null);

        return newBitmap;
    }

    // -------------------------------------------------------------------------

    public static Bitmap loadBitmap(int resId, int maxWidth, int maxHeight) {
        final Context ctx = Utilities.getApplicationContext();
        return loadBitmap(ctx, resId, maxWidth, maxHeight);
    }

    public static Bitmap loadBitmap(Context ctx, int resId, int maxWidth, int maxHeight) {
        if (ctx == null) {
            ctx = Utilities.getApplicationContext();
        }

        final Resources res = ctx.getResources();

        InputStream is = null;
        try {
            TypedValue tv = new TypedValue();
            is = res.openRawResource(resId, tv);
        } catch (Exception e) {
            LOG.logE("ERROR load from res: " + resId, e);
            return null;
        }

        final int width = maxWidth;
        final int height = maxHeight;
        if (width <= 0 || height <= 0) {
            try {
                return BitmapFactory.decodeStream(is);
            } catch (Throwable tr) {
                LOG.logE("ERROR load from res: " + resId, tr);
                return null;
            }
        }

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, options);

        try {
            is.reset();
        } catch (Exception e) {
            LOG.logI("stream could not be reseted, re-create...");
            try {
                Utilities.silentlyClose(is);
                is = res.openRawResource(resId);
            } catch (Exception ee) {
                return null;
            }
        }

        if (options.outWidth == width && options.outHeight == height) {
            return BitmapFactory.decodeStream(is);
        }

        if (options.outHeight == -1 || options.outWidth == -1) {
            LOG.logI("Error get bitmap dimension: " + resId);
            return BitmapFactory.decodeStream(is);
        }

        // Calculate inSampleSize
        options.inSampleSize = Math.max(Math.min(options.outHeight / height, options.outWidth / width), 1);
        LOG.logI("sample bitmap: " + resId + ", sample=" + options.inSampleSize + ", bmpWidth=" + options.outWidth
                + ", bmpHeight=" + options.outHeight + ", width=" + width + ", height=" + height);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        // DO NOT ZOOM HERE
        // caller must zoom the bitmap to expected size, it might has its own aspect / size requirement
        return BitmapFactory.decodeStream(is, null, options);
    }

    public static Bitmap loadBitmap(String path, int maxWidth, int maxHeight) {
        LOG.logI("load bitmap from: " + path + ", maxW=" + maxWidth + ", maxW=" + maxHeight);

        if (path != null) {
            if (path.startsWith("content://")) {
                return loadBitmapFromProvider(path, maxWidth, maxHeight);
            } else {
                return loadBitmapFromFile(path, maxWidth, maxHeight);
            }
        }

        return null;
    }

    private static Bitmap loadBitmapFromFile(String path, int screenWidth, int screenHeight) {
        final int width = screenWidth;
        final int height = screenHeight;
        if (width <= 0 || height <= 0) {
            return BitmapFactory.decodeFile(path);
        }

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        if (options.outWidth == width && options.outHeight == height) {
            return BitmapFactory.decodeFile(path);
        }

        if (options.outHeight == -1 || options.outWidth == -1) {
            LOG.logI("Error get bitmap dimension: " + path);
            return BitmapFactory.decodeFile(path);
        }

        // Calculate inSampleSize
        options.inSampleSize = Math.max(Math.min(options.outHeight / height, options.outWidth / width), 1);
        LOG.logI("sample bitmap: " + path + ", sample=" + options.inSampleSize + ", bmpWidth=" + options.outWidth
                + ", bmpHeight=" + options.outHeight + ", width=" + width + ", height=" + height);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        // DO NOT ZOOM HERE
        // caller must zoom the bitmap to expected size, it might has its own aspect / size requirement
        return BitmapFactory.decodeFile(path, options);
    }

    private static Bitmap loadBitmapFromProvider(String path, int screenWidth, int screenHeight) {
        final Context ctx = Utilities.getApplicationContext();

        InputStream is = null;
        try {
            is = ctx.getContentResolver().openInputStream(Uri.parse(path));
        } catch (FileNotFoundException e) {
            LOG.logE("ERROR load from provider: " + path, e);
            return null;
        }

        final int width = screenWidth;
        final int height = screenHeight;
        if (width <= 0 || height <= 0) {
            try {
                return BitmapFactory.decodeStream(is);
            } catch (Throwable tr) {
                LOG.logE("ERROR load from provider: " + path, tr);
                return null;
            }
        }

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, options);

        try {
            is.reset();
        } catch (Exception e) {
            LOG.logI("stream could not be reseted, re-create...");
            try {
                Utilities.silentlyClose(is);
                is = ctx.getContentResolver().openInputStream(Uri.parse(path));
            } catch (FileNotFoundException fne) {
                LOG.logE("ERROR load from provider: " + path, fne);
                return null;
            }
        }

        if (options.outWidth == width && options.outHeight == height) {
            return BitmapFactory.decodeStream(is);
        }

        if (options.outHeight == -1 || options.outWidth == -1) {
            LOG.logI("Error get bitmap dimension: " + path);
            return BitmapFactory.decodeStream(is);
        }

        // Calculate inSampleSize
        options.inSampleSize = Math.max(Math.min(options.outHeight / height, options.outWidth / width), 1);
        LOG.logI("sample bitmap: " + path + ", sample=" + options.inSampleSize + ", bmpWidth=" + options.outWidth
                + ", bmpHeight=" + options.outHeight + ", width=" + width + ", height=" + height);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        // DO NOT ZOOM HERE
        // caller must zoom the bitmap to expected size, it might has its own aspect / size requirement
        return BitmapFactory.decodeStream(is, null, options);
    }

    public static void saveBitmap(Bitmap bitmap, String filePath) {
        if (bitmap == null || bitmap.isRecycled()) {
            return;
        }
        File f = new File(filePath);
        if (f.exists()) {
            f.delete();
        }

        try {
            FileOutputStream out = new FileOutputStream(f);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.flush();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static BitmapFactory.Options getBitmapSize(String filePath) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
        options.inJustDecodeBounds = false;
        return options;
    }

    public static int getImageOrientation(String imageLocalPath) {
        try {
            ExifInterface exifInterface = new ExifInterface(imageLocalPath);
            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
            return orientation;
        } catch (IOException e) {
            e.printStackTrace();
            return ExifInterface.ORIENTATION_NORMAL;
        }
    }

    /**
     * 获得真实的图片宽高
     * 解决从相册选择图片时setImageView被旋转的问题
     * https://blog.csdn.net/weixin_33991418/article/details/91460109
     * */
    public static Size getImageRealSize(String imageLocalPath) {
        BitmapFactory.Options options =  getBitmapSize(imageLocalPath);
        int width = options.outWidth;
        int height = options.outHeight;
        int orientation = getImageOrientation(imageLocalPath);
        switch(orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
            case ExifInterface.ORIENTATION_ROTATE_270: {
                return new Size(height, width);
            }
            default: {
                return new Size(width, height);
            }
        }
    }
}
